﻿using System;
using System.Linq;
using System.Xml.Linq;
using System.Reflection;
using System.Collections.Generic;

namespace svnxf
{
  class Program
  {
    public static void Main(String[] args)
    {
      Console.WriteLine(
        "{0} {1}", Assembly.GetEntryAssembly().GetName().Name, Assembly.GetEntryAssembly().GetName().Version);

      #region handle user input

      var argsInfo = new ArgsInfo();
      if (args.Length == 0 || args[0] == "help")
      {
        Console.WriteLine(Utils.GetFileContents(Environment.CurrentDirectory + "\\svnxfhelp.txt"));
        Environment.Exit(0);
      }
      else
      {
        argsInfo = ParseArgs(args);

        if(string.IsNullOrEmpty(argsInfo.Path))
        {
          Console.WriteLine("Please enter a valid location prefixed with --path=");
          Environment.Exit(0);
        }

        if (argsInfo.Depth == null)
          argsInfo.Depth = "--depth=infinity";
      }
      #endregion

      //Get all properties of type svn:externals and load into XDocument      
      Console.WriteLine("[1/4]Searching path for external properties...");

      var svnArg = string.Format("propget --xml {0} svn:externals {1}", argsInfo.Depth, argsInfo.Path);
      var svnProps = XDocument.Load(Utils.SvnCommand(svnArg));

      //Extract FolderPath and FolderProperty from XDocment and fill folderinfo object
      Console.WriteLine("[2/4]Extracting Folder path and properties...");
      var externalsInfo = ExtractExternalsAndFillFolderInfo(svnProps);

      //Get svn external last commit revision and populate FolderInfo collection
      Console.WriteLine("[3/4]Searching externals last commit revision...");
      var folderInfosList = GetRevisionAndFillFolderInfo(externalsInfo);
      
      //create's temp folder, apply's modified external and commits.
      Console.WriteLine("[4/4]Freezing Externals...");
      var isValid = FreezeExternals(folderInfosList);

      if(isValid)
        Console.WriteLine("External Freeze Complete.");
    }

    #region Helper Methods

    private static ArgsInfo ParseArgs(IEnumerable<string> args)
    {
      var info = new ArgsInfo();
      foreach (var arg in args)
      {
        if (arg.Contains("--depth="))
        {
          info.Depth = arg;
        }

        if (arg.Contains("--path="))
        {
          info.Path = Utils.RegexSplit(arg, "--path=").ToArray().GetValue(0).ToString();
        }
      }
      return info;
    }

    private static IEnumerable<FolderInfo> ExtractExternalsAndFillFolderInfo(XContainer allExternals)
    {
      var externals =
        from external in allExternals.Descendants("target")
        where external.Element("property").Attribute("name").Value == "svn:externals"
        select new FolderInfo
                 {
                   FolderPath = external.Attribute("path").Value,
                   FolderProperty = external.Element("property").Value
                 };
      return externals.ToList();
    }

    private static IEnumerable<FolderInfo> GetRevisionAndFillFolderInfo(IEnumerable<FolderInfo> folderInfos)
    {
      var externalsList = new List<FolderInfo>();
      foreach (var info in folderInfos)
      {
        info.FolderProperty.Trim();
        //clean-up carriage return line feeds, to get externals on each line
        info.FolderProperty =
          Utils.RegexReplace(info.FolderProperty, @"(\r\n|\r|\n)+", Environment.NewLine);

        //parse individual externals from folder svn:external property
        var externals =
          Utils.RegexSplit(info.FolderProperty, Environment.NewLine);

        //Go through each external and prefix the line with last commit revision
        var externalsInfo = new FolderInfo();
        foreach (var line in externals)
        {
          var processExternal = false;
          var externalSourceAndTarget = line.Trim().Split(' ');

          //Don't process external if empty or already has a revision
          if (!string.IsNullOrEmpty(externalSourceAndTarget[0]))
            if (!line.ToLower().Contains("-r"))
              if(!line.ToLower().Contains("--revision"))
                processExternal = true;

          //Retrieve last commit revision for the external and add as prefix
          if (processExternal)
          {
            //Get XDocument from svn containing info for a given external
            var xmlSvnInfo = XDocument.Load(
              Utils.SvnCommand(string.Format("info --xml {0}{1}", Utils.GetUrlDomain(info.FolderPath), externalSourceAndTarget[0])));

            //Retrieve the commit revision for the external
            var externalRev =
              xmlSvnInfo.Descendants("entry").Elements("commit").Attributes("revision").Single().Value;

            //Build external with revision
            externalsInfo.FolderExternal.Add(string.Format("  -r {0} {1}", externalRev, line));
          }
          else
          {
            externalsInfo.FolderExternal.Add("  " + line);
          }
        }
        externalsInfo.FolderPath = info.FolderPath;
        externalsInfo.FolderProperty = info.FolderProperty;
        externalsList.Add(externalsInfo);
      }
      return externalsList;
    }

    private static bool FreezeExternals(IEnumerable<FolderInfo> folderInfosList)
    {
      var isValid = false;
      const string externalsTempRoot = "TempExternals";

      try
      {
        if (folderInfosList.ToArray().Length <= 0)
        {
          Console.WriteLine("No Externals Found.");
        }
        else
        {
          Console.WriteLine("Frozen folders: ");
          foreach (var info in folderInfosList)
          {
            //Create temporary directory to modify externals
            string externalsTempDirectory = string.Format("{0}\\{1}", externalsTempRoot, DateTime.Now.TimeOfDay.Ticks);
            Utils.CreateDirectory(externalsTempDirectory);

            //checkout single folder where the external is located
            Utils.SvnCommand(string.Format("checkout --depth=empty {0} {1}", info.FolderPath, externalsTempDirectory));

            //delete temp directory external
            Utils.SvnCommand(string.Format("propdel svn:externals {0}", externalsTempDirectory));

            //Join all externals separated by NewLine and add as folder property type svn:externals
            var joined = string.Join(Environment.NewLine, info.FolderExternal.ToArray());
            Utils.SvnCommand(string.Format(@"propset svn:externals ""{0}"" {1}", joined, externalsTempDirectory));

            //commit new external
            Utils.SvnCommand(
              string.Format(@"commit -m ""Automated script to freeze externals."" {0}",externalsTempDirectory));

            Console.WriteLine(info.FolderPath);
          }
          //Cleanup and delete temp directory
          Utils.DeleteDirectory(externalsTempRoot);
          isValid = true;
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.InnerException);
      }

      return isValid;
    }

    #endregion
  }
}